package golangAps

import (
	"bytes"
	"encoding/gob"
	"errors"
	"github.com/nutsdb/nutsdb"
	"runtime"
	"time"
)

var DefaultBucket = "default"
var DefaultRWMode = nutsdb.MMap
var DefaultDss []nutsdb.DataStructure = []nutsdb.DataStructure{
	nutsdb.DataStructureSet,
	nutsdb.DataStructureSortedSet,
	nutsdb.DataStructureBTree,
	nutsdb.DataStructureList}

type Store interface {
	// Initialization functions for each store,
	// called when the scheduler run `SetStore`.
	Init() error

	// close connect, when remove store
	Close() error

	// Add job to this store.
	AddJob(j Job) error

	// Get the job from this store.
	//  @return error `JobNotFoundError` if there are no job.
	GetJob(id string) (Job, error)

	// Get all jobs from this store.
	GetAllJobs() ([]Job, error)

	// 获取当前需要执行的任务
	GetDueJobs(timestamp int64) ([]Job, error)

	// Update job in store with a newer version.
	UpdateJob(j Job) error

	// Delete the job from this store.
	DeleteJob(id string) error

	// Get the earliest next run time of all the jobs stored in this store,
	// or `time.Time{}` if there are no job.
	// Used to set the wakeup interval for the scheduler.
	GetNextRunTime() (int64, error)

	// Clear all resources bound to this store.
	Clear() error

	StateDump(j Job) ([]byte, error)
	StateLoad(state []byte) (Job, error)
}

// 空的bucket 创建后 重启直接读取会导致 err bucket 错误
func NewStore(dataDir string) *StoreNutsdb {
	if dataDir == "" {
		dataDir = "nutsdb"
	}

	db, err := nutsdb.Open(
		nutsdb.DefaultOptions,
		nutsdb.WithEntryIdxMode(nutsdb.HintKeyAndRAMIdxMode),
		nutsdb.WithRWMode(DefaultRWMode),
		nutsdb.WithSyncEnable(true),
		nutsdb.WithDir(dataDir), // 数据库会自动创建这个目录文件
	)
	if err != nil {
		DefaultLog.Error(Context{}, err.Error())
	}
	return &StoreNutsdb{
		db:            db,
		saveJobKey:    []byte("saveJob.nutsdb"),
		runningJobKey: []byte("runningJob.nutsdb"),
	}
}

type StoreNutsdb struct {
	db            *nutsdb.DB
	saveJobKey    []byte
	runningJobKey []byte
}

func (s *StoreNutsdb) Init() error { return nil }

func (s *StoreNutsdb) Close() error {
	return s.db.Close()
}

func (s *StoreNutsdb) Merge() error {
	return s.db.Merge()
}

func (s *StoreNutsdb) AddJob(j Job) error {
	state, err := s.StateDump(j)

	err = s.db.Update(func(tx *nutsdb.Tx) error {
		err = tx.Put(j.StoreName, []byte(j.Id), state, 0)
		if err != nil {
			return err
		}
		err = tx.ZAdd(j.StoreName, s.saveJobKey, float64(j.NextRunTime), []byte(j.Id))
		if err != nil {
			return err
		}

		return nil
	})
	return err
}

func (s *StoreNutsdb) GetJob(bucket, id string) (j Job, err error) {
	var job []byte = make([]byte, 0)
	err = s.db.View(func(tx *nutsdb.Tx) error {
		jb, err := tx.Get(bucket, []byte(id))
		if err != nil {
			return err
		}
		job = jb
		return nil
	})
	if err != nil {
		err = JobNotFoundError(id)
		return
	}

	j, err = s.StateLoad(job)

	return j, err
}

func (s *StoreNutsdb) GetAllJobs(bucket string) ([]Job, error) {
	jobs := make([]Job, 0)
	values := make([][]byte, 0)
	err := s.db.View(func(tx *nutsdb.Tx) error {
		if v, err := tx.GetValues(bucket); err != nil {
			return err
		} else {
			values = v
			return nil
		}
	})

	var errs error = nil
	for _, v := range values {
		if j, err := s.StateLoad(v); err != nil {
			errs = errors.Join(errs, err)
			continue
		} else {
			jobs = append(jobs, j)
		}
	}

	return jobs, err
}

func (s *StoreNutsdb) GetDueJobs(bucket string, timestamp int64) ([]Job, error) {
	jobList := make([]Job, 0)
	var jobs [][]byte = make([][]byte, 0)
	err := s.db.Update(func(tx *nutsdb.Tx) error {
		score, err := tx.ZRangeByScore(bucket, s.saveJobKey, 0, float64(timestamp), nil)
		if err != nil {
			if errors.Is(err, nutsdb.ErrSortedSetNotFound) {
				// 没有数据
				return nil
			}
			return err
		}

		// 把这些id都插入到   运行中， 外部添加更新的时候需要检查一下这个id是否已经存在了
		jobIds := make([][]byte, 0)
		for _, v := range score {
			job, err := tx.Get(bucket, v.Value)
			if err != nil {
				return err
			}
			jobs = append(jobs, job)
			jobIds = append(jobIds, v.Value)
		}
		// 加锁
		err = tx.SAdd(bucket, s.runningJobKey, jobIds...)
		if err != nil {
			return err
		}
		return nil
	})
	// 这个解析的过程不要放在 事物中
	for _, job := range jobs {
		j, er := s.StateLoad(job)
		if err != nil {
			err = errors.Join(err, er)
			continue
		}
		jobList = append(jobList, j)
	}

	return jobList, err
}

func (s *StoreNutsdb) UpdateJob(j Job) error {
	return s.AddJob(j)
}

// 更新任务 前提是任务不在执行中
func (s *StoreNutsdb) UpdateJobWithNoRunning(j Job) error {
	state, err := s.StateDump(j)
	// 这里需要一个自旋锁
	var isRun = false
	for {
		s.db.View(func(tx *nutsdb.Tx) error {
			if ok, _ := tx.SIsMember(j.StoreName, s.runningJobKey, []byte(j.Id)); ok {
				isRun = true
				return nil
			}
			isRun = false
			return nil
		})
		if isRun {
			// 让出时间片
			runtime.Gosched()
			time.Sleep(time.Millisecond * 5)
			continue
		}

		// 更新
		err = s.db.Update(func(tx *nutsdb.Tx) error {
			err = tx.Put(j.StoreName, []byte(j.Id), state, 0)
			if err != nil {
				return err
			}
			err = tx.ZAdd(j.StoreName, s.saveJobKey, float64(j.NextRunTime), []byte(j.Id))
			if err != nil {
				return err
			}

			return nil
		})
		return err
	}
}

func (s *StoreNutsdb) updateJob(tx *nutsdb.Tx, j Job) error {
	state, err := s.StateDump(j)
	err = tx.Put(j.StoreName, []byte(j.Id), state, 0)
	if err != nil {
		return err
	}
	err = tx.ZAdd(j.StoreName, s.saveJobKey, float64(j.NextRunTime), []byte(j.Id))
	if err != nil {
		return err
	}
	return nil

}

func (s *StoreNutsdb) DeleteJob(bucket string, id []byte) error {
	return s.db.Update(func(tx *nutsdb.Tx) error {
		err := tx.Delete(bucket, id)
		if err != nil {
			return err
		}
		err = tx.ZRem(bucket, s.saveJobKey, id)
		if err != nil {
			return err
		}

		return nil
	})
}

func (s *StoreNutsdb) DeleteJobWithNoRunning(bucket string, id []byte) error {
	var isRun = false
	for {
		s.db.View(func(tx *nutsdb.Tx) error {
			if ok, _ := tx.SIsMember(bucket, s.runningJobKey, id); ok {
				isRun = true
				return nil
			}
			isRun = false
			return nil
		})
		if isRun {
			// 让出时间片
			runtime.Gosched()
			time.Sleep(time.Millisecond * 5)
			continue
		}

		// 更新
		err := s.db.Update(func(tx *nutsdb.Tx) error {
			err := tx.Delete(bucket, id)
			if err != nil {
				return err
			}
			err = tx.ZRem(bucket, s.saveJobKey, id)
			if err != nil {
				return err
			}

			return nil
		})
		return err
	}
}

func (s *StoreNutsdb) deleteJob(tx *nutsdb.Tx, bucket string, id []byte) error {
	err := tx.Delete(bucket, id)
	if err != nil {
		return err
	}
	err = tx.ZRem(bucket, s.saveJobKey, id)
	if err != nil {
		return err
	}
	return nil
}

// Clear 删除指定 bucket 下的所有数据
func (s *StoreNutsdb) Clear(bucket string) error {
	return s.db.Update(func(tx *nutsdb.Tx) error {
		var errs error
		for _, ds := range DefaultDss {
			err := tx.DeleteBucket(ds, bucket)
			errs = errors.Join(errs, err)
		}
		return errs
	})
}

func (s *StoreNutsdb) GetNextRunTime(bucket string) (int64, error) {
	var nextRunTime int64 = 0
	err := s.db.View(func(tx *nutsdb.Tx) error {
		score, err := tx.ZPeekMin(bucket, s.saveJobKey)
		if err != nil {
			return err
		}
		nextRunTime = int64(score.Score)
		return nil
	})
	return nextRunTime, err
}

func (s *StoreNutsdb) StateDump(j Job) ([]byte, error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(j)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func (s *StoreNutsdb) StateLoad(state []byte) (Job, error) {
	var j Job
	buf := bytes.NewBuffer(state)
	dec := gob.NewDecoder(buf)
	err := dec.Decode(&j)
	if err != nil {
		return Job{}, err
	}

	return j, nil
}

func (s *StoreNutsdb) SetBucket(bucket string) (bool, error) {
	createOk := true
	err := s.db.Update(func(tx *nutsdb.Tx) error {
		var errs error
		for _, ds := range DefaultDss {
			if !tx.ExistBucket(ds, bucket) {
				if err := tx.NewBucket(ds, bucket); err != nil {
					errs = errors.Join(errs, err)
				}
				DefaultLog.Info(Context{}, "add new store", "ds", ds, "store", bucket)
			} else {
				createOk = false
			}
		}

		return nil
	})
	return createOk, err
}

func (s *StoreNutsdb) GetAllBuckets() ([]string, error) {
	storeNames := make([]string, 0)

	err := s.db.View(func(tx *nutsdb.Tx) error {
		return tx.IterateBuckets(nutsdb.DataStructureSet, "*", func(bucket string) bool {
			storeNames = append(storeNames, bucket)
			return true
		})
	})
	return storeNames, err
}
